<!DOCTYPE html>
<html lang="zh_cn">
<head>
          <title>来玩魔王的咚</title>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta charset="utf-8" />
        <!-- twitter card metadata -->
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="/images/mowang.png">
<meta name="twitter:site" content="">
<meta name="twitter:title" content="shell 命令行参数（getopt和getopts）">
<meta name="twitter:description" content="<p>getopt命令：在命令行中使用，支持长选项，指定错误报告的程序名，可选参数；脚本中使用getopt：set命令，eval命令，解决参数空格问题，参数解析错误并退出；getopts命令：错误报告模式，示例代码；将选项标准化</p>">
        <!-- OG Tags -->
<meta property="og:url" content="/shell-command-line-parameters-getopt-and-getopts.html"/>
<meta property="og:title" content="来玩魔王的咚 | shell 命令行参数（getopt和getopts）" />
<meta property="og:description" content="<p>getopt命令：在命令行中使用，支持长选项，指定错误报告的程序名，可选参数；脚本中使用getopt：set命令，eval命令，解决参数空格问题，参数解析错误并退出；getopts命令：错误报告模式，示例代码；将选项标准化</p>" />
        <!-- favicon -->
        <link rel="icon" type="image/png" href="/images/mowang.png">
        <!-- moment.js for date formatting -->
        <script src="/theme/js/moment.js"></script>
        <!-- css -->
        <link rel="stylesheet" type="text/css" href="/theme/css/main.css" />
        <!-- 左边的menu，如果页面高度不够，就跟着滚动，否则文章分类显示不全 -->
        <link rel="stylesheet" type="text/css" href="/theme/css/mycss/menu.css" />
		<script>
			
                /*! grunt-grunticon Stylesheet Loader - v2.1.2 | https://github.com/filamentgroup/grunticon | (c) 2015 Scott Jehl, Filament Group, Inc. | MIT license. */
    
    (function(e){function t(t,n,r,o){"use strict";function a(){for(var e,n=0;u.length>n;n++)u[n].href&&u[n].href.indexOf(t)>-1&&(e=!0);e?i.media=r||"all":setTimeout(a)}var i=e.document.createElement("link"),l=n||e.document.getElementsByTagName("script")[0],u=e.document.styleSheets;return i.rel="stylesheet",i.href=t,i.media="only x",i.onload=o||null,l.parentNode.insertBefore(i,l),a(),i}var n=function(r,o){"use strict";if(r&&3===r.length){var a=e.navigator,i=e.Image,l=!(!document.createElementNS||!document.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect||!document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image","1.1")||e.opera&&-1===a.userAgent.indexOf("Chrome")||-1!==a.userAgent.indexOf("Series40")),u=new i;u.onerror=function(){n.method="png",n.href=r[2],t(r[2])},u.onload=function(){var e=1===u.width&&1===u.height,a=r[e&&l?0:e?1:2];n.method=e&&l?"svg":e?"datapng":"png",n.href=a,t(a,null,null,o)},u.src="",document.documentElement.className+=" grunticon"}};n.loadCSS=t,e.grunticon=n})(this);(function(e,t){"use strict";var n=t.document,r="grunticon:",o=function(e){if(n.attachEvent?"complete"===n.readyState:"loading"!==n.readyState)e();else{var t=!1;n.addEventListener("readystatechange",function(){t||(t=!0,e())},!1)}},a=function(e){return t.document.querySelector('link[href$="'+e+'"]')},c=function(e){var t,n,o,a,c,i,u={};if(t=e.sheet,!t)return u;n=t.cssRules?t.cssRules:t.rules;for(var l=0;n.length>l;l++)o=n[l].cssText,a=r+n[l].selectorText,c=o.split(");")[0].match(/US\-ASCII\,([^"']+)/),c&&c[1]&&(i=decodeURIComponent(c[1]),u[a]=i);return u},i=function(e){var t,o,a;o="data-grunticon-embed";for(var c in e)if(a=c.slice(r.length),t=n.querySelectorAll(a+"["+o+"]"),t.length)for(var i=0;t.length>i;i++)t[i].innerHTML=e[c],t[i].style.backgroundImage="none",t[i].removeAttribute(o);return t},u=function(t){"svg"===e.method&&o(function(){i(c(a(e.href))),"function"==typeof t&&t()})};e.embedIcons=i,e.getCSS=a,e.getIcons=c,e.ready=o,e.svgLoadedCallback=u,e.embedSVG=u})(grunticon,this);
                
                grunticon(["/theme/css/icons.data.svg.css", "/theme/css/icons.data.png.css", "/theme/css/icons.fallback.css"]);
            </script>
        <noscript><link href="/theme/css/icons.fallback.css" rel="stylesheet"></noscript>
        <!-- menu toggle javascript -->
        <script type="text/javascript">
            document.addEventListener("DOMContentLoaded", initMenu);
            
            function initMenu(){
                var menu = document.getElementById("menu");
                var menulink = document.getElementById("menu-link");
                menulink.addEventListener("click", function toggleMenu(){
                        window.event.preventDefault();
                        menulink.classList.toggle('active');
                        menu.classList.toggle('active');              
                    });
            };
        </script>
        <!-- 不蒜子 -->
        <script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>

    <meta name="description" content="<p>getopt命令：在命令行中使用，支持长选项，指定错误报告的程序名，可选参数；脚本中使用getopt：set命令，eval命令，解决参数空格问题，参数解析错误并退出；getopts命令：错误报告模式，示例代码；将选项标准化</p>" />

    <meta name="tags" content="shell" />
    <meta name="tags" content="命令行参数" />
  <!-- 替换部分base的样式，看文章时，要再宽一点，右边有很多空间可以撑开 -->
  <link rel="stylesheet" type="text/css" href="/theme/css/mycss/article.css" />

</head>
<body>
    <div role="banner" id="masthead">
        <header>
            <a href="/"><img src="/images/mowang.png" alt="McManus Logo"></a>
                <h1>来玩魔王的咚@骑士救兵</h1>
            <a href="#menu" id="menu-link">more stuff</a>
            <nav id="menu">
                <ul>
                        <li><a href="/tags">tags</a></li>
                            <li><a href="/category/cloud.html">Cloud</a></li>
                            <li><a href="/category/docker.html">Docker</a></li>
                            <li><a href="/category/go.html">Go</a></li>
                            <li class="active"><a href="/category/linux.html">Linux</a></li>
                            <li><a href="/category/python.html">Python</a></li>
                            <li><a href="/category/xue-xi-bi-ji.html">学习笔记</a></li>
                            <li><a href="/category/yun-wei-zi-dong-hua.html">运维自动化</a></li>
                </ul>
            </nav>
        </header>
    </div>
        <div class="page" role="main">
  <div class="article" role="article">
    <article>
        <footer>
            <a name="top"></a>
            <p>
              <time datetime=" 2020-09-14 15:00:00+08:00">
                <script>document.write(moment('2020-09-14 15:00:00+08:00').format('LL'));</script>
              </time>
              ~
              <time datetime=" 2020-09-14 15:00:00+08:00">
                <script>document.write(moment('2020-09-14 15:00:00+08:00').format('LL'));</script>
              </time>
            </p>
        </footer>
        <header>
          <h2>
            shell 命令行参数（getopt和getopts）
          </h2>
        </header>
      <div class="content">
         <div class="toc">
<ul>
<li><a href="#getopt-ming-ling">getopt 命令</a><ul>
<li><a href="#zai-ming-ling-xing-zhong-jian-dan-shi-yong">在命令行中简单使用</a></li>
<li><a href="#zhi-chi-zhang-xuan-xiang">支持长选项</a></li>
<li><a href="#cuo-wu-bao-gao-yin-yong-de-cheng-xu-ming">错误报告引用的程序名</a></li>
<li><a href="#ke-xuan-can-shu">可选参数</a></li>
<li><a href="#xiao-jie">小结</a></li>
</ul>
</li>
<li><a href="#zai-jiao-ben-zhong-shi-yong-getopt">在脚本中使用 getopt</a><ul>
<li><a href="#set-ming-ling">set 命令</a></li>
<li><a href="#zhi-jie-shi-yong">直接使用</a></li>
<li><a href="#shi-yong-di-er-zhong-ge-shi-lai-jie-xi">使用第二种格式来解析</a></li>
<li><a href="#jie-jue-kong-ge-wen-ti">解决空格问题</a></li>
<li><a href="#can-shu-jie-xi-cuo-wu-bing-tui-chu">参数解析错误并退出</a></li>
<li><a href="#wan-zheng-de-dai-ma-shi-li">完整的代码示例</a></li>
</ul>
</li>
<li><a href="#getopts-ming-ling">getopts 命令</a><ul>
<li><a href="#ji-ben-yong-fa">基本用法</a></li>
<li><a href="#cuo-wu-bao-gao-mo-shi">错误报告模式</a></li>
<li><a href="#shi-li-dai-ma">示例代码</a></li>
</ul>
</li>
<li><a href="#jiang-xuan-xiang-biao-zhun-hua">将选项标准化</a></li>
</ul>
</div>
<p>上篇：<a href="https://steeed.gitee.io/shell-command-line-parameters-basic.html">https://steeed.gitee.io/shell-command-line-parameters-basic.html</a>  </p>
<h3 id="getopt-ming-ling"><a class="toclink" href="#getopt-ming-ling">getopt 命令</a></h3>
<p>使用getopt命令，可以解析任何命令行选项和参数，但是用法比较复杂。getopt的命令用法如下：</p>
<div class="highlight"><pre><span></span><code>$ getopt --help

用法：
 getopt optstring parameters
 getopt <span class="o">[</span>options<span class="o">]</span> <span class="o">[</span>--<span class="o">]</span> optstring parameters
 getopt <span class="o">[</span>options<span class="o">]</span> -o<span class="p">|</span>--options optstring <span class="o">[</span>options<span class="o">]</span> <span class="o">[</span>--<span class="o">]</span> parameters

选项：
 -a, --alternative            允许长选项以 - 开始
 -h, --help                   这个简短的用法指南
 -l, --longoptions &lt;长选项&gt;  要识别的长选项
 -n, --name &lt;程序名&gt;         将错误报告给的程序名
 -o, --options &lt;选项字符串&gt;  要识别的短选项
 -q, --quiet                  禁止 getopt<span class="o">(</span><span class="m">3</span><span class="o">)</span> 的错误报告
 -Q, --quiet-output           无正常输出
 -s, --shell &lt;shell&gt;          设置 shell 引用规则
 -T, --test                   测试 getopt<span class="o">(</span><span class="m">1</span><span class="o">)</span> 版本
 -u, --unquoted               不引用输出
 -V, --version                输出版本信息

$ 
</code></pre></div>

<p>用法一共有3种格式，下面都会用到。  </p>
<h4 id="zai-ming-ling-xing-zhong-jian-dan-shi-yong"><a class="toclink" href="#zai-ming-ling-xing-zhong-jian-dan-shi-yong">在命令行中简单使用</a></h4>
<p>先看第一个最简单的格式：</p>
<div class="highlight"><pre><span></span><code>getopt optstring parameters
</code></pre></div>

<p>第一部分是命令名。<br>
第二部分optstring（选项字符串），是这个命令解析的格式。<br>
第三部分parameters（getopt命令的参数），就是需要解析的内容。<br>
因此，getopt会按照 optstring 的设置，将 parameters 解析为相应的选项和参数。参考示例来理解：</p>
<div class="highlight"><pre><span></span><code>$ getopt ab:cd -ad value1 -b best1 value2 value3
 -a -d -b best1 -- value1 value2 value3
$ 
</code></pre></div>

<p>主要理解 <code>ab:cd</code> 的意义。  </p>
<p>这里定义的都是短选项，4个字母代表有4个选项。b后面的冒号表示这个选项需要一个参数。<br>
如果不给选项b一个参数，就会报错：</p>
<div class="highlight"><pre><span></span><code>$ getopt ab:cd -ad value1 -b
getopt：选项需要一个参数 -- b
 -a -d -- value1
$ 
</code></pre></div>

<h5 id="shi-yong-shuang-po-zhe-xian"><a class="toclink" href="#shi-yong-shuang-po-zhe-xian">使用双破折线</a></h5>
<p>如果添加了双破折线，那么无轮后面是什么，都会作为参数而不是选项来处理：</p>
<div class="highlight"><pre><span></span><code>$ getopt ab:cd -- -ad value1 -b best1 value2 value3
 -- -ad value1 -b best1 value2 value3
$ getopt ab:cd -ad value1 -- -b best1 value2 value3
 -a -d -- value1 -b best1 value2 value3
$ 
</code></pre></div>

<p><em>注意，这里的值是没有用引号包起来的。</em><br>
这依然是是命令用法的第一种格式，双破折线是parameters内容的一部分。<br>
双破折线出现位置之前的内容按照optstring的设置来解析，之后的内容一律认为是参数。即使有类似选项的内容，被认作为是参数。  </p>
<h5 id="can-shu-bao-han-kong-ge-de-wen-ti"><a class="toclink" href="#can-shu-bao-han-kong-ge-de-wen-ti">参数包含空格的问题</a></h5>
<p>第一种格式和第二、第三种在功能上也是有区别的。<br>
上面的例子里，输出的值都是没有被引号包起来的。而另外两种格式输出的参数都是带引号的（见下面的例子）。<br>
重要的区别不在引号上，而是这种用法不支持处理带空格和引号的参数值。它会将空格当作参数分隔符，而不是根据双引号将二者当作一个参数。<em>因为把这里不带引号的结果传递给之后的处理函数，空格就是两个值的分隔符。而如果有引号，那么引号中的值无论是否有空格认作是一个值。</em>  </p>
<h4 id="zhi-chi-zhang-xuan-xiang"><a class="toclink" href="#zhi-chi-zhang-xuan-xiang">支持长选项</a></h4>
<p>参考上面的示例，加上长选项的支持。使用长选项的示例如下：</p>
<div class="highlight"><pre><span></span><code>$ getopt -o ab:cd --long arga,argb:,argc,argd -- -ad -b best --argd value1 value2
 -a -d -b <span class="s1">&#39;best&#39;</span> --argd -- <span class="s1">&#39;value1&#39;</span> <span class="s1">&#39;value2&#39;</span>
$ 
</code></pre></div>

<p><em>注意，这里的值用引号包起来了。</em><br>
这是命令用法的第三种格式。<br>
-o 表示定义短选项<br>
--long 其实是--longoptions，不过省略任意个字母程序都能认识。或者也可以用-l。这个是指定长选项的。所有内容都要连起来，不能有空格。选项之间用逗号隔开。定义完之后，在用双破折号隔开，后面的内容就是parameters。  </p>
<h4 id="cuo-wu-bao-gao-yin-yong-de-cheng-xu-ming"><a class="toclink" href="#cuo-wu-bao-gao-yin-yong-de-cheng-xu-ming">错误报告引用的程序名</a></h4>
<p>之前已经试过一次解析错误的报告了：</p>
<div class="highlight"><pre><span></span><code>$ getopt ab:cd -ad value1 -b
getopt：选项需要一个参数 -- b
 -a -d -- value1
$ 
</code></pre></div>

<p>这里错误报告的是getopt错误，可以把这个默认的内容替换掉。一般是换成执行的脚本的名字。<br>
这里使用命令用法的第二种格式，把 optstring 和 parameters 都放到双破折线后面：</p>
<div class="highlight"><pre><span></span><code>$ getopt -- ab:cd -ad value1 -b best1
 -a -d -b <span class="s1">&#39;best1&#39;</span> -- <span class="s1">&#39;value1&#39;</span>
$ 
</code></pre></div>

<p>这样在双破折线前面就可加getopt命令的选项，这里要指定-n选项：</p>
<div class="highlight"><pre><span></span><code>$ getopt -n test.sh -- ab:cd -ad value1 -b 
test.sh：选项需要一个参数 -- b
 -a -d -- <span class="s1">&#39;value1&#39;</span>
$ 
</code></pre></div>

<p>这里看到包裹错误是，名字已经被替换掉了。<br>
在脚本中，可以使用 <code>$(basename $0)</code> 或者直接用$0。  </p>
<h5 id="jin-zhi-cuo-wu-bao-gao"><a class="toclink" href="#jin-zhi-cuo-wu-bao-gao">禁止错误报告</a></h5>
<p>还有一个-q参数，可以禁止错误报告，解析错误的选项和参数将被丢弃：</p>
<div class="highlight"><pre><span></span><code>$ getopt -n test.sh -q -- ab:cd -ad value1 -b 
 -a -d -- <span class="s1">&#39;value1&#39;</span>
$ 
</code></pre></div>

<h4 id="ke-xuan-can-shu"><a class="toclink" href="#ke-xuan-can-shu">可选参数</a></h4>
<p>还有一种可选参数，使用两个冒号。这个选项可以有一个或零个参数：</p>
<div class="highlight"><pre><span></span><code>$ getopt -o a::bc: -l arga::,argb,argc: -- -a value1 --arga value2
 -a <span class="s1">&#39;&#39;</span> --arga <span class="s1">&#39;&#39;</span> -- <span class="s1">&#39;value1&#39;</span> <span class="s1">&#39;value2&#39;</span>
$ getopt -o a::bc: -l arga::,argb,argc: -- -avalue1 --arga<span class="o">=</span>value2
 -a <span class="s1">&#39;value1&#39;</span> --arga <span class="s1">&#39;value2&#39;</span> --
$ 
</code></pre></div>

<p>第一次执行是传递的参数是错误的。因为是可选参数，参数和值之间不能有空格隔开，否则会有歧义。必须要连在一起才能认为是前一个选项的参数。否则就被认作是独立的参数了。  </p>
<h4 id="xiao-jie"><a class="toclink" href="#xiao-jie">小结</a></h4>
<p>getopt 命令的选项所指定的选项字符串的规则：</p>
<ul>
<li>短选项，每一个字符代表一个选项</li>
<li>长选项，每一个字符串代表一个选项，用逗号分隔</li>
<li>选项后跟一个冒号，表示选项需要一个参数</li>
<li>选项后跟两个冒号，表示选项有一个可选参数（一个或零个参数）</li>
<li>可选参数的参数和值之间不能有空格，短选项直接连起来，长选项加等号连起来</li>
</ul>
<h3 id="zai-jiao-ben-zhong-shi-yong-getopt"><a class="toclink" href="#zai-jiao-ben-zhong-shi-yong-getopt">在脚本中使用 getopt</a></h3>
<p>现在已经可以用getopt命令，将命令行参数按照规定的格式解析成规整的格式了。并且在解析过程中，还能发现参数格式错误的情况并报告。<br>
接下来就是在脚本中使用经过getopt命令解析后的参数了。具体的使用，参考后面的示例代码。<br>
<em>下面举例的使用也直接运行了脚本，脚本的具体内容就是后面的示例代码。</em>  </p>
<h4 id="set-ming-ling"><a class="toclink" href="#set-ming-ling">set 命令</a></h4>
<p>要在脚本中使用getopt。首先，要用getopt命令生成格式化后的版本来替换已有的命令行选项和参数。需要用到set命令。<br>
set命令能够处理shell中的各种变量。具体不展开，这里只用了这个命令的一个选项，双破折线（--）。效果是将命令行参数替换成set命令的参数值。<br>
然后，该方法会将原始脚本的命令行参数传给getopt命令执行，之后再将getopt命令的输出传给set命令，用getopt格式化后的命令行参数来替换原始的命令行参数：</p>
<div class="highlight"><pre><span></span><code><span class="nb">set</span> -- <span class="k">$(</span>getopt ab:cd <span class="s2">&quot;</span><span class="nv">$@</span><span class="s2">&quot;</span><span class="k">)</span>
</code></pre></div>

<p>现在原始的命令行参数变量的值会被getopt命令的输出替换。而getopt已经为我们格式化好了命令行参数。  </p>
<h4 id="zhi-jie-shi-yong"><a class="toclink" href="#zhi-jie-shi-yong">直接使用</a></h4>
<p>在之前编写的脚本的基础上，只要在开头加上一行代码，就可以直接使用了：</p>
<div class="highlight"><pre><span></span><code><span class="nb">set</span> -- <span class="k">$(</span>getopt a:b:s:u <span class="s2">&quot;</span><span class="nv">$@</span><span class="s2">&quot;</span><span class="k">)</span>
</code></pre></div>

<p>加上这句后，就是让后续的代码处理getopt返回的参数，而不是调用命令时的命令行参数。  </p>
<p>验证效果：</p>
<div class="highlight"><pre><span></span><code>$ ./format.sh -u -a after -b befor value1 value2 value3
BEFOR_VALUE1_AFTER
BEFOR_VALUE2_AFTER
BEFOR_VALUE3_AFTER
$ ./format.sh -u -a after -b befor value1 <span class="s2">&quot;value2 value3&quot;</span> value4
BEFOR_VALUE1_AFTER
BEFOR_VALUE2_AFTER
BEFOR_VALUE3_AFTER
BEFOR_VALUE4_AFTER
$ 
</code></pre></div>

<p>第二条命令并不能处理带空格的参数，因为这里使用的是getopt的第一种格式。  </p>
<h4 id="shi-yong-di-er-zhong-ge-shi-lai-jie-xi"><a class="toclink" href="#shi-yong-di-er-zhong-ge-shi-lai-jie-xi">使用第二种格式来解析</a></h4>
<p>要处理空格，就需要使用第二种格式（或者第三种），将命令修改为如下：</p>
<div class="highlight"><pre><span></span><code><span class="nb">set</span> -- <span class="k">$(</span>getopt -- a:b:s:u <span class="s2">&quot;</span><span class="nv">$@</span><span class="s2">&quot;</span><span class="k">)</span>
</code></pre></div>

<p>简单的在最前面加上双破折线就好了。这条语句是错误的，后面还要修改。  </p>
<p>再来验证一下：</p>
<div class="highlight"><pre><span></span><code>$ ./format.sh -u -a after -b befor value1 <span class="s2">&quot;value2 value3&quot;</span> value4
<span class="s1">&#39;BEFOR&#39;</span>_<span class="s1">&#39;VALUE1&#39;</span>_<span class="s1">&#39;AFTER&#39;</span>
<span class="s1">&#39;BEFOR&#39;</span>_<span class="s1">&#39;VALUE2_&#39;</span>AFTER<span class="s1">&#39;</span>
<span class="s1">&#39;</span>BEFOR<span class="s1">&#39;_VALUE3&#39;</span>_<span class="s1">&#39;AFTER&#39;</span>
<span class="s1">&#39;BEFOR&#39;</span>_<span class="s1">&#39;VALUE4&#39;</span>_<span class="s1">&#39;AFTER&#39;</span>
$ 
</code></pre></div>

<p>使用第二、第三种格式，会用引号来限定参数的内容。但是引号干扰了set命令。  </p>
<h5 id="shi-yong-eval-ming-ling"><a class="toclink" href="#shi-yong-eval-ming-ling">使用 eval 命令</a></h5>
<p>这里出现了一个新的问题，不但没有正确的处理空格，输出的内容还有额外的引号。空格的问题先放一放，这里需要用到eval命令来解决新问题。  </p>
<blockquote>
<p>eval 命令用于将其后的内容作为单个命令读取和执行，这里用于处理getopt命令生成的参数的转义字符。  </p>
</blockquote>
<p>关于eval命令，还有一种使用的情景。有时候在脚本中拼接出来的字符串即使打印出来看正确。并且直接复制、粘贴在交互界面中也能正确读被当做命令运行。但是却无法在脚本中被执行。这个时候就可以使用eval命令来解决。它能够把字符串当做命令来执行。<br>
在脚本中通过各种引用和判断拼接出一个复杂的命令的时候，有时候就会出现无法执行的情况。这时候就直接赋值、粘贴去交换界面试一下，如果拼接的结果本身没问题，那么加上eval命令后，应该就能用运行。  </p>
<p>修改命令如下：</p>
<div class="highlight"><pre><span></span><code><span class="nb">eval</span> <span class="nb">set</span> -- <span class="k">$(</span>getopt -- a:b:s:u <span class="s2">&quot;</span><span class="nv">$@</span><span class="s2">&quot;</span><span class="k">)</span>
</code></pre></div>

<p>再次验证：</p>
<div class="highlight"><pre><span></span><code>$ ./format.sh -u -a after -b befor value1 <span class="s2">&quot;value2 value3&quot;</span> value4
BEFOR_VALUE1_AFTER
BEFOR_VALUE2 VALUE3_AFTER
BEFOR_VALUE4_AFTER
$ 
</code></pre></div>

<p>第一种格式加上eval命令也是没有问题的，所以可以无脑用上。  </p>
<p>如果使用getopt的第一种格式。那么不支持有空格的参数值，不过因为输出不带引号，也不需要eval命令来处理。但是加上eval命令也没不会有问题。<br>
如果使用getopt的第二、第三种格式。那么输出的值带引号，支持参数值有空格，但是需要用eval命令来特殊处理一下。  </p>
<h4 id="jie-jue-kong-ge-wen-ti"><a class="toclink" href="#jie-jue-kong-ge-wen-ti">解决空格问题</a></h4>
<p>只要能正确的使用getopt的第二种或第三种格式，那么参数包含空格的问题也就解决了。看上一小节。  </p>
<h4 id="can-shu-jie-xi-cuo-wu-bing-tui-chu"><a class="toclink" href="#can-shu-jie-xi-cuo-wu-bing-tui-chu">参数解析错误并退出</a></h4>
<p>执行命令时，使用错误的参数，当前的效果如下：</p>
<div class="highlight"><pre><span></span><code>$ ./format.sh -u -w -a after -b befor value1 <span class="s2">&quot;value2 value3&quot;</span> value4
getopt：无效选项 -- w
BEFOR_VALUE1_AFTER
BEFOR_VALUE2 VALUE3_AFTER
BEFOR_VALUE4_AFTER
$ 
</code></pre></div>

<p>解析发现问题了，并且报告了，但是脚本没有终止，而是继续执行。如果要判断出解析错误，就需要使用$?参数。然后退出脚本则是用exit命令。<br>
这里直接直接使用$?并无法获取到参数解析错误的结果。因为此时的结果是set命令（也可能是eval命令）的执行结果，而getopt是再前一条的命令。<br>
解决这个问题，要先把getopt命令执行一遍，进行判断。然后再用set调用一遍，可以直接使用之前执行的结果：</p>
<div class="highlight"><pre><span></span><code><span class="nv">getopt_cmd</span><span class="o">=</span><span class="k">$(</span>getopt -n <span class="k">$(</span>basename <span class="nv">$0</span><span class="k">)</span> -- a:b:s:u <span class="s2">&quot;</span><span class="nv">$@</span><span class="s2">&quot;</span><span class="k">)</span>
<span class="o">[</span> <span class="nv">$?</span> -ne <span class="m">0</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">exit</span> <span class="m">1</span>
<span class="nb">eval</span> <span class="nb">set</span> -- <span class="s2">&quot;</span><span class="nv">$getopt_cmd</span><span class="s2">&quot;</span>
</code></pre></div>

<p>这里还加上了报告错误时名称的定义。exit退出时也要指定退出状态为非0，因为是运行错误。  </p>
<p>验证效果：</p>
<div class="highlight"><pre><span></span><code>$ ./format.sh -v -a after -w -b
format.sh：无效选项 -- v
format.sh：无效选项 -- w
format.sh：选项需要一个参数 -- b
$ <span class="nb">echo</span> <span class="nv">$?</span>
<span class="m">1</span>
$ 
</code></pre></div>

<p>现在解析有问题后，就会直接退出。  </p>
<h4 id="wan-zheng-de-dai-ma-shi-li"><a class="toclink" href="#wan-zheng-de-dai-ma-shi-li">完整的代码示例</a></h4>
<p>这里加上长选项以及可选参数的功能。<br>
多加了一个参数 <code>-m, --mark</code> 由于指定使用什么连接符：</p>
<ul>
<li>默认直接连，不使用连接符号</li>
<li>加上选项，默认使用下划线连接</li>
<li>为选项加上参数后，则使用参数来连接</li>
</ul>
<p>参数比较多，加了 <code>-h, --help</code> 选项打印参数说明。  </p>
<p>完整代码如下：</p>
<div class="highlight"><pre><span></span><code>$ cat format.sh
<span class="c1">#!/bin/bash</span>
<span class="nv">mark</span><span class="o">=</span><span class="s2">&quot;&quot;</span>      <span class="c1"># 连接符号</span>
<span class="nv">prefix</span><span class="o">=</span><span class="s2">&quot;&quot;</span>    <span class="c1"># 前缀</span>
<span class="nv">base</span><span class="o">=</span><span class="s2">&quot;test&quot;</span>  <span class="c1"># 默认字符串</span>
<span class="nv">suffix</span><span class="o">=</span><span class="s2">&quot;&quot;</span>    <span class="c1"># 后缀</span>
<span class="nv">upper</span><span class="o">=</span>off    <span class="c1"># 是否大写</span>
<span class="c1"># 显示声明一下这是个数组变量，其实没有必要</span>
<span class="nb">declare</span> -a names  <span class="c1"># 需要格式化输出的所有原始字符串</span>
<span class="c1"># 打印的帮助信息</span>
<span class="nv">help_str</span><span class="o">=</span><span class="s2">&quot;</span>
<span class="s2">参数说明：</span>
<span class="s2">  -h, --help:           打印帮助信息</span>
<span class="s2">  -m, --mark [连接符]:  使用连接符，默认是下划线（_），可以指定</span>
<span class="s2">  -a, --after string:   添加后缀</span>
<span class="s2">  -b, --befor string:   添加前缀</span>
<span class="s2">  -s, --string string:  指定中间的字符串，默认是“test”</span>
<span class="s2">  -u, --upper:          全大写输出</span>
<span class="s2">&quot;</span>
<span class="c1"># 解析命令行参数</span>
<span class="nv">getopt_cmd</span><span class="o">=</span><span class="k">$(</span>getopt -o m::ha:b:s:u --long mark::,help,after:,befor:,string:,upper -n <span class="k">$(</span>basename <span class="nv">$0</span><span class="k">)</span> -- <span class="s2">&quot;</span><span class="nv">$@</span><span class="s2">&quot;</span><span class="k">)</span>
<span class="o">[</span> <span class="nv">$?</span> -ne <span class="m">0</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">exit</span> <span class="m">1</span>
<span class="nb">eval</span> <span class="nb">set</span> -- <span class="s2">&quot;</span><span class="nv">$getopt_cmd</span><span class="s2">&quot;</span>
<span class="c1"># 解析选项</span>
<span class="k">while</span> <span class="o">[</span> -n <span class="s2">&quot;</span><span class="nv">$1</span><span class="s2">&quot;</span> <span class="o">]</span>
<span class="k">do</span>
    <span class="k">case</span> <span class="s2">&quot;</span><span class="nv">$1</span><span class="s2">&quot;</span> <span class="k">in</span>
        -m<span class="p">|</span>--mark<span class="o">)</span>
            <span class="k">case</span> <span class="s2">&quot;</span><span class="nv">$2</span><span class="s2">&quot;</span> <span class="k">in</span>
                <span class="s2">&quot;&quot;</span><span class="o">)</span>
                    <span class="nv">mark</span><span class="o">=</span><span class="s2">&quot;_&quot;</span>
                    <span class="nb">shift</span> <span class="p">;;</span>
                *<span class="o">)</span>
                    <span class="nv">mark</span><span class="o">=</span><span class="s2">&quot;</span><span class="nv">$2</span><span class="s2">&quot;</span>
                    <span class="nb">shift</span> <span class="p">;;</span>
            <span class="k">esac</span>
            <span class="p">;;</span>
        -h<span class="p">|</span>--help<span class="o">)</span>
            <span class="nb">echo</span> -e <span class="s2">&quot;</span><span class="nv">$help_str</span><span class="s2">&quot;</span>
            <span class="nb">exit</span> <span class="p">;;</span>
        -a<span class="p">|</span>--after<span class="o">)</span>
            <span class="nv">suffix</span><span class="o">=</span><span class="s2">&quot;</span><span class="nv">$2</span><span class="s2">&quot;</span>
            <span class="nb">shift</span> <span class="p">;;</span>
        -b<span class="p">|</span>--befor<span class="o">)</span>
            <span class="nv">prefix</span><span class="o">=</span><span class="s2">&quot;</span><span class="nv">$2</span><span class="s2">&quot;</span>
            <span class="nb">shift</span> <span class="p">;;</span>
        -s<span class="p">|</span>--string<span class="o">)</span>
            <span class="nv">base</span><span class="o">=</span><span class="s2">&quot;</span><span class="nv">$2</span><span class="s2">&quot;</span>
            <span class="nb">shift</span> <span class="p">;;</span>
        -u<span class="p">|</span>--upper<span class="o">)</span>
            <span class="nv">upper</span><span class="o">=</span>on <span class="p">;;</span>
        --<span class="o">)</span> <span class="nb">shift</span>
            <span class="nb">break</span> <span class="p">;;</span>
         *<span class="o">)</span> <span class="nb">echo</span> <span class="s2">&quot;</span><span class="nv">$1</span><span class="s2"> is not an option&quot;</span>
            <span class="nb">exit</span> <span class="m">1</span> <span class="p">;;</span>  <span class="c1"># 发现未知参数，直接退出</span>
    <span class="k">esac</span>
    <span class="nb">shift</span>
<span class="k">done</span>
<span class="c1"># 解析参数</span>
<span class="k">while</span> <span class="o">[</span> -n <span class="s2">&quot;</span><span class="nv">$1</span><span class="s2">&quot;</span> <span class="o">]</span>
<span class="k">do</span>
    <span class="nv">names</span><span class="o">=(</span><span class="s2">&quot;</span><span class="si">${</span><span class="nv">names</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&quot;</span> <span class="s2">&quot;</span><span class="nv">$1</span><span class="s2">&quot;</span><span class="o">)</span>
    <span class="nb">shift</span>
<span class="k">done</span>
names<span class="o">[</span><span class="m">0</span><span class="o">]=</span><span class="si">${</span><span class="nv">names</span><span class="p">[0]</span><span class="k">:-</span><span class="nv">$base</span><span class="si">}</span>
<span class="k">for</span> name <span class="k">in</span> <span class="s2">&quot;</span><span class="si">${</span><span class="nv">names</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="k">do</span>
    <span class="c1"># 添加前缀和后缀</span>
    <span class="nv">output</span><span class="o">=</span><span class="s2">&quot;</span><span class="si">${</span><span class="nv">prefix</span><span class="p">:+</span><span class="si">${</span><span class="nv">prefix</span><span class="si">}${</span><span class="nv">mark</span><span class="si">}}${</span><span class="nv">name</span><span class="si">}${</span><span class="nv">suffix</span><span class="p">:+</span><span class="si">${</span><span class="nv">mark</span><span class="si">}${</span><span class="nv">suffix</span><span class="si">}}</span><span class="s2">&quot;</span>
    <span class="c1"># 判断是否要全大写输出</span>
    <span class="k">if</span> <span class="o">[</span> <span class="nv">$upper</span> <span class="o">=</span> on <span class="o">]</span>
    <span class="k">then</span>
        <span class="nv">output</span><span class="o">=</span><span class="si">${</span><span class="nv">output</span><span class="p">^^</span><span class="si">}</span>
    <span class="k">fi</span>
    <span class="c1"># 输出结果</span>
    <span class="nb">echo</span> <span class="s2">&quot;</span><span class="nv">$output</span><span class="s2">&quot;</span>
<span class="k">done</span>
$ 
</code></pre></div>

<p>验证效果：</p>
<div class="highlight"><pre><span></span><code>$ ./format.sh -a after -b befor VALUE1 <span class="s2">&quot;VALUE2 VALUE3&quot;</span> VALUE4
beforVALUE1after
beforVALUE2 VALUE3after
beforVALUE4after
$ ./format.sh -a after -b befor --mark 
befor_test_after
$ ./format.sh -a after -b befor --mark<span class="o">=</span><span class="s2">&quot;||&quot;</span> -u
BEFOR<span class="o">||</span>TEST<span class="o">||</span>AFTER
$ ./format.sh -a after -b befor --mark<span class="o">=</span><span class="s2">&quot;||&quot;</span> -u --help

参数说明：
  -h, --help:           打印帮助信息
  -m, --mark <span class="o">[</span>连接符<span class="o">]</span>:  使用连接符，默认是下划线（_），可以指定
  -a, --after string:   添加后缀
  -b, --befor string:   添加前缀
  -s, --string string:  指定中间的字符串，默认是“test”
  -u, --upper:          全大写输出

$ 
</code></pre></div>

<p>有getopt就够用了。顺便再简单讲下getopts。<br>
getopts功能上差一点，不过封装的更高级，用起来更简单，需要的代码也会少一点。  </p>
<h3 id="getopts-ming-ling"><a class="toclink" href="#getopts-ming-ling">getopts 命令</a></h3>
<p>getopts是另一个解析命令行参数的工具。它是Bash的内部命令。<br>
它的优势在于：</p>
<ul>
<li>不需要通过一个外部程序来处理位置参数</li>
<li>可以很容易地设置用来解析的Shell变量</li>
<li>getopts 定义在 POSIX 中</li>
</ul>
<p>不支持长选项：  </p>
<blockquote>
<p>getopts 不能解析 GUN 风格的长选项（--long），也不能解析 XF86 风格的长选项（-long）  </p>
</blockquote>
<p>getopt 是将选项和参数处理后只生成一个输出。我们还要用 set 来完成传递的工作。<br>
getopts 能够和已有的shell参数变量配合默契。每次调用时，一次只处理命令行上检测到的一个参数。处理之后，它会退出并返回一个大于0的退出状态码。这样就非常方便的可以在while循环中使用。  </p>
<h4 id="ji-ben-yong-fa"><a class="toclink" href="#ji-ben-yong-fa">基本用法</a></h4>
<p>getopts 会使用到一下3个变量：<br>
OPTIND: 存放下一个要处理的参数的索引。这是 getopts 在调用过程中记住自己状态的方式。<br>
OPTARG: 由 getopts 找到的选项所对应的参数。<br>
OPTERR: 值为0或1。指示Bash是否应该显示由 getopts 产生的错误信息。  </p>
<p>getopts 命令的基本语法：</p>
<div class="highlight"><pre><span></span><code>getopts 选项字符串 名称 [参数]
</code></pre></div>

<p>选项字符串（OPTSTRING）：getopts 会有哪些选项，哪些是有参数的（选项后有冒号）<br>
名称（VARNAME）：getopts 会将找到的选项赋值给这个名称的变量<br>
参数（ARGS）：一般情况向缺省，getopts会去解析脚本调用时的所有的参数。如果执行了这个参数，getopts就不解析传递给脚本的参数了，而是解析这里的参数。  </p>
<p>getopts 不会移动变量。在处理完所有的选项后，命令就会停止，并将参数留给我们来继续处理。此时可以先用shit命令配合OPTIND的值来移动到第一个参数的位置：</p>
<div class="highlight"><pre><span></span><code>shift $[ $OPTIND - 1 ]
</code></pre></div>

<h4 id="cuo-wu-bao-gao-mo-shi"><a class="toclink" href="#cuo-wu-bao-gao-mo-shi">错误报告模式</a></h4>
<p>getopts命令支持两种错误报告模式：</p>
<ul>
<li>详细错误报告模式</li>
<li>抑制错误报告模式</li>
</ul>
<p>对于产品中的脚本，推荐使用抑制错误报告模式。  </p>
<h5 id="xiang-xi-cuo-wu-bao-gao-mo-shi"><a class="toclink" href="#xiang-xi-cuo-wu-bao-gao-mo-shi">详细错误报告模式</a></h5>
<p>在详细错误报告模式下，如果 getopts 遇到了一个无效的选项，VARNAME 的值会被设置为问号（?），并且变量 OPTARG 不会被设置。如果需要的参数没找到，VARNAME的值也会被设置为问号（?），变量 OPRARG 也不会被设置，并且会打印一个错误信息。  </p>
<h5 id="yi-zhi-cuo-wu-bao-gao-mo-shi"><a class="toclink" href="#yi-zhi-cuo-wu-bao-gao-mo-shi">抑制错误报告模式</a></h5>
<p>在抑制错误报告模式下，如果 getopts 遇到一个无效的选项，VARNAME 的值会被设置为问号（?），并且变量 OPTARG 会被设置为选项字符。如果需要的参数没找到，VARNAME的值会被设置为冒号（:），并且变量 OPTARG 中会包含选项字符。<br>
要使用抑制错误报告模式，只需要在调用 getopts 时，设置选项字符串（OPTSTRING）时以冒号开头即可。下面的例子用的就是一直错误报告模式。  </p>
<h4 id="shi-li-dai-ma"><a class="toclink" href="#shi-li-dai-ma">示例代码</a></h4>
<p>这里使用抑制错误报告模式，所以需要自己分析并且报告解析错误。都在代码里了：</p>
<div class="highlight"><pre><span></span><code>$ cat say_hello.sh 
<span class="c1">#!/bin/bash</span>
<span class="nv">defaultname</span><span class="o">=</span><span class="s2">&quot;nobody&quot;</span>  <span class="c1"># 默认的名字</span>
<span class="nb">declare</span> -a names      <span class="c1"># 存放名字的数组</span>
<span class="nv">hello</span><span class="o">=</span><span class="s2">&quot;hello&quot;</span>         <span class="c1"># 打招呼的用语</span>
<span class="nv">end</span><span class="o">=</span><span class="s2">&quot;!&quot;</span>               <span class="c1"># 结束的内容</span>
<span class="nv">tittle</span><span class="o">=</span>off            <span class="c1"># 是否首字母大写</span>
<span class="c1"># 解析选项</span>
<span class="k">while</span> <span class="nb">getopts</span> :n:h:e:t opt
<span class="k">do</span>
    <span class="k">case</span> <span class="s2">&quot;</span><span class="nv">$opt</span><span class="s2">&quot;</span> <span class="k">in</span>
        n<span class="o">)</span> <span class="nv">defaultname</span><span class="o">=</span><span class="s2">&quot;</span><span class="nv">$OPTARG</span><span class="s2">&quot;</span> <span class="p">;;</span>
        h<span class="o">)</span> <span class="nv">hello</span><span class="o">=</span><span class="s2">&quot;</span><span class="nv">$OPTARG</span><span class="s2">&quot;</span> <span class="p">;;</span>
        e<span class="o">)</span> <span class="nv">end</span><span class="o">=</span><span class="s2">&quot;</span><span class="nv">$OPTARG</span><span class="s2">&quot;</span> <span class="p">;;</span>
        t<span class="o">)</span> <span class="nv">tittle</span><span class="o">=</span>on <span class="p">;;</span>
        :<span class="o">)</span>  <span class="c1"># 没有为需要参数的选项指定参数</span>
            <span class="nb">echo</span> <span class="s2">&quot;This option -</span><span class="nv">$OPTARG</span><span class="s2"> requires an argument.&quot;</span>
            <span class="nb">exit</span> <span class="m">1</span> <span class="p">;;</span>
        ?<span class="o">)</span> <span class="c1"># 发现了无效的选项</span>
            <span class="nb">echo</span> <span class="s2">&quot;-</span><span class="nv">$OPTARG</span><span class="s2"> is not an option&quot;</span>
            <span class="nb">exit</span> <span class="m">2</span> <span class="p">;;</span>
    <span class="k">esac</span>
<span class="k">done</span>
<span class="c1"># 解析参数</span>
<span class="nb">shift</span> $<span class="o">[</span> <span class="nv">$OPTIND</span> -1 <span class="o">]</span>  <span class="c1"># 移动到第一个参数的位置</span>
<span class="c1"># 这次用for循环遍历</span>
<span class="k">for</span> arg <span class="k">in</span> <span class="s2">&quot;</span><span class="nv">$@</span><span class="s2">&quot;</span>
<span class="k">do</span>
    <span class="nv">names</span><span class="o">=(</span><span class="s2">&quot;</span><span class="si">${</span><span class="nv">names</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&quot;</span> <span class="s2">&quot;</span><span class="nv">$arg</span><span class="s2">&quot;</span><span class="o">)</span>
<span class="k">done</span>
names<span class="o">[</span><span class="m">0</span><span class="o">]=</span><span class="si">${</span><span class="nv">names</span><span class="p">[0]</span><span class="k">:-</span><span class="nv">$defaultname</span><span class="si">}</span>
<span class="k">for</span> name <span class="k">in</span> <span class="s2">&quot;</span><span class="si">${</span><span class="nv">names</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="k">do</span>
    <span class="o">[</span> <span class="s2">&quot;</span><span class="nv">$tittle</span><span class="s2">&quot;</span> <span class="o">=</span> on <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nv">output</span><span class="o">=</span><span class="s2">&quot;</span><span class="si">${</span><span class="nv">hello</span><span class="p">^</span><span class="si">}</span><span class="s2"> </span><span class="si">${</span><span class="nv">name</span><span class="p">^</span><span class="si">}</span><span class="s2"> </span><span class="nv">$end</span><span class="s2">&quot;</span> <span class="o">||</span> <span class="nv">output</span><span class="o">=</span><span class="s2">&quot;</span><span class="nv">$hello</span><span class="s2"> </span><span class="nv">$name</span><span class="s2"> </span><span class="nv">$end</span><span class="s2">&quot;</span>
    <span class="nb">echo</span> <span class="s2">&quot;</span><span class="si">${</span><span class="nv">output</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="k">done</span>
$ 
</code></pre></div>

<p>验证执行如下：</p>
<div class="highlight"><pre><span></span><code>$ ./say_hello.sh 
hello nobody !
$ ./say_hello.sh -n adam
hello adam !
$ ./say_hello.sh -n adam -h hi -e. -t
Hi Adam .
$ ./say_hello.sh -h hi -e. -t adam bob clark
Hi Adam .
Hi Bob .
Hi Clark .
$ ./say_hello.sh -a -h hi -e. -t adam bob clark
-a is not an option
$ ./say_hello.sh -h
This option -h requires an argument.
$ 
</code></pre></div>

<p>选项和参数不能混排：</p>
<div class="highlight"><pre><span></span><code>$ ./say_hello.sh adam
hello adam !
$ ./say_hello.sh adam -t
hello adam !
hello -t !
$ 
</code></pre></div>

<p>支持双破折线：</p>
<div class="highlight"><pre><span></span><code>$ ./say_hello.sh -t adam
Hello Adam !
$ ./say_hello.sh -t -- adam
Hello Adam !
$ 
</code></pre></div>

<p>比较下来，使用起来会比getopt方便很多，不过功能上也差了很多，可选参数（双冒号::）应该也是不支持的。另外，如果熟悉getopt的话，每一步的操作都是自己的代码控制的。而getopts就简化了很多地方，比如不会调用shift移动变量。  </p>
<h3 id="jiang-xuan-xiang-biao-zhun-hua"><a class="toclink" href="#jiang-xuan-xiang-biao-zhun-hua">将选项标准化</a></h3>
<p>有些字母选项在Linux世界里已经有了某种程度的标准含义。如果在shell脚本中支持这些选项，就应该使用标准的字母来定义。<br>
下面是一些命令行选项中经常会用到的选项和含义：</p>
<table>
<thead>
<tr>
<th>选项</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>-a</td>
<td>显示所有对象（显示隐藏）</td>
</tr>
<tr>
<td>-c</td>
<td>生成一个计数</td>
</tr>
<tr>
<td>-d</td>
<td>指定一个目录</td>
</tr>
<tr>
<td>-e</td>
<td>扩展一个对象</td>
</tr>
<tr>
<td>-f</td>
<td>指定读入数据的文件</td>
</tr>
<tr>
<td>-h</td>
<td>显示命令的帮助信息</td>
</tr>
<tr>
<td>-i</td>
<td>忽略文本大小写</td>
</tr>
<tr>
<td>-l</td>
<td>产生输出的长格式版本</td>
</tr>
<tr>
<td>-n</td>
<td>使用非交互模式（批处理）</td>
</tr>
<tr>
<td>-o</td>
<td>将所有输出重定向到指定的输出文件</td>
</tr>
<tr>
<td>-q</td>
<td>以安静模式运行</td>
</tr>
<tr>
<td>-r</td>
<td>递归地处理目录和文件</td>
</tr>
<tr>
<td>-s</td>
<td>以安静模式运行</td>
</tr>
<tr>
<td>-v</td>
<td>生成详细输出</td>
</tr>
<tr>
<td>-x</td>
<td>排除某个对象</td>
</tr>
<tr>
<td>-y</td>
<td>对所有问题回答yes</td>
</tr>
</tbody>
</table>
      </div>
      <div class="back-to-top">
        <a href="/">HOME</a>
        <a href="#top">TOP</a>
      </div>
    </article>
  </div>
<!-- end article -->
<!-- 页面往下滚动一段之后才会显示TOC -->
<script>
  window.onscroll = function() {
    var tocbox = document.getElementsByClassName('toc')[0];
    var osTop = document.documentElement.scrollTop || document.body.scrollTop;
    var osWidth = document.documentElement.scrollWidth || document.body.scrollWidth;
    // console.log(osTop)
    if (osTop>300 && osWidth>865) {
      tocbox.style.display = "block"
    }
    if (osTop<300 || osWidth<865) {
      tocbox.style.display = "none"
    }
  }
</script>
                <footer>
                    <div class="icons">
                    </div>
                    <span id="busuanzi_container_page_pv" style="padding: 10px">本文阅读量<span id="busuanzi_value_page_pv"></span>次</span>
                    <span id="busuanzi_container_site_pv" style="padding: 10px">本站总访问量<span id="busuanzi_value_site_pv"></span>次</span>
                    <span id="busuanzi_container_site_uv" style="padding: 10px">本站总访客数<span id="busuanzi_value_site_uv"></span>人</span>
                    <p>© <script>document.write(moment().format('YYYY'));</script> 749B</p>
                </footer>
        </div>
</body>
</html>